﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CID.Domain.Development;
using CID.Domain.Infrastructure;
using CID.Execution.Agent.Tests.Support;
using CID.Execution.Support.Contracts;
using CID.Execution.Support.Information;
using CID.Execution.Support.Scripting;
using NUnit.Framework;
using Rhino.Mocks;

namespace CID.Execution.Agent.Tests
{
    [TestFixture]
    class BaseScriptExecuterTests : BaseAgentTests
    {
        #region Stubs
        private class ScriptStepStub : ScriptStep
        {
            private static readonly ITemplateType TemplateTypeStub;
            private static readonly ITemplate TemplateStub;

            static ScriptStepStub()
            {
                TemplateTypeStub = MockRepository.GenerateStub<ITemplateType>();
                TemplateTypeStub.Name = "Some Template Type";
                TemplateTypeStub.Stub(x => x.Id).Return(0);
                TemplateStub = MockRepository.GenerateStub<ITemplate>();
                TemplateStub.Name = "Some Template";
                TemplateStub.Stub(x => x.Id).Return(0);
                TemplateStub.Stub(x => x.Type).Return(TemplateTypeStub);
            }

            public ScriptStepStub(bool successful = true) : base(TemplateStub, new IParameterValue[0])
            {
                Executed = false;
                Successful = successful;
            }

            public bool Successful { get; private set; }

            public bool Executed { get; set; }
        }

        private class ScriptStub : IScript<ScriptStepStub>
        {
            public static readonly IRelease ReleaseStub = MockRepository.GenerateStub<IRelease>();
            
            static ScriptStub()
            {
                ReleaseStub.Stub(x => x.Id).Return(0);
                ReleaseStub.Name = "Some Release";
            }

            public ScriptStub(params ScriptStepStub[] steps)
            {
                Steps = steps;
                Release = new ReleaseInfo(ReleaseStub);
                DeploymentUniqueIdentifier = Guid.NewGuid();
                LocationId = 0;
            }

            public IEnumerable<ScriptStepStub> Steps { get; private set; }

            public ReleaseInfo Release { get; private set; }

            public Guid DeploymentUniqueIdentifier { get; private set; }

            public int LocationId { get; private set; }
        }

        private class ScriptExecuterStub : BaseScriptExecuter<ScriptStub, ScriptStepStub>
        {
            public const string FailingStepErrorMessage = "Failure on purpose";
            public bool OnExecutionStartedCalled { get; private set; }
            public bool OnExecutionEndedCalled { get; private set; }
            public int ExecuteStepCallCount { get; private set; }
            public bool ExecutionEndedWithError { get; private set; }
            public string ExecutionDetails { get; private set; }

            public ScriptExecuterStub(ScriptStub script) : base(script) 
            {
                OnExecutionStartedCalled = OnExecutionEndedCalled = ExecutionEndedWithError = false;
                ExecuteStepCallCount = 0;
                ExecutionDetails = String.Empty;
            }

            public IControllerService CreateControllerServiceProxy()
            {
                return ControllerServiceProxyFactory.Create();
            }

            public IEnumerable<ParameterValueInfo> GetAdditionalParameterValues()
            {
                return GetGeneralAdditionalParameterValues();
            }

            protected override void ExecuteStep(ScriptStepStub step, StringBuilder detailsBuilder)
            {
                ExecuteStepCallCount++;
                if (!step.Successful) throw new Exception(FailingStepErrorMessage);
                step.Executed = true;
            }

            protected override void OnExecutionStarted(ScriptStub script, StringBuilder detailsBuilder)
            {
                OnExecutionStartedCalled = true;
            }

            protected override void OnExecutionEnded(ScriptStub script, string details, bool error)
            {
                OnExecutionEndedCalled = true;
                ExecutionEndedWithError = error;
                ExecutionDetails = details;
            }
        }
        #endregion

        private ScriptStub script;

        private ScriptExecuterStub scriptExecuter;

        public override void SetUp()
        {
            base.SetUp();

            script = new ScriptStub(new ScriptStepStub(), new ScriptStepStub(), new ScriptStepStub());
            scriptExecuter = new ScriptExecuterStub(script);
        }

        #region Constructor
        [Test]
        public void CreateNew_ShouldAssignProvidedScriptAndInjectControllerServiceFactory()
        {
            var script = new ScriptStub(new ScriptStepStub());
            
            var scriptExecuter = new ScriptExecuterStub(script);

            Assert.AreEqual(script, scriptExecuter.Script);
            Assert.IsInstanceOf<ControllerServiceProxyStub>(scriptExecuter.CreateControllerServiceProxy());
            Assert.AreEqual(0, scriptExecuter.ExecuteStepCallCount);
            Assert.IsFalse(scriptExecuter.OnExecutionStartedCalled);
            Assert.IsFalse(scriptExecuter.OnExecutionEndedCalled);
        }
        #endregion

        #region Execute
        [Test]
        public void Execute_UseThreeSuccessfulScripts_ShouldSucceed()
        {
            scriptExecuter.Execute();

            Assert.IsTrue(scriptExecuter.OnExecutionStartedCalled);
            Assert.IsTrue(scriptExecuter.OnExecutionEndedCalled);
            Assert.AreEqual(scriptExecuter.Script.Steps.Count(), scriptExecuter.ExecuteStepCallCount);
            Assert.IsFalse(scriptExecuter.ExecutionEndedWithError);
        }

        [Test]
        public void Execute_UseThreeFailingScripts_ShouldSucceed()
        {
            scriptExecuter = new ScriptExecuterStub(new ScriptStub(new ScriptStepStub(false), new ScriptStepStub(false), new ScriptStepStub(false)));
            
            scriptExecuter.Execute();

            Assert.IsTrue(scriptExecuter.OnExecutionStartedCalled);
            Assert.IsTrue(scriptExecuter.OnExecutionEndedCalled);
            Assert.AreNotEqual(scriptExecuter.Script.Steps.Count(), scriptExecuter.ExecuteStepCallCount);
            Assert.AreEqual(1, scriptExecuter.ExecuteStepCallCount);
            Assert.IsTrue(scriptExecuter.ExecutionEndedWithError);
            StringAssert.Contains(ScriptExecuterStub.FailingStepErrorMessage, scriptExecuter.ExecutionDetails);
        }

        [Test]
        public void Execute_UseTwoSuccessfulScriptsAndTowFailingScripts_ShouldSucceed()
        {
            scriptExecuter = new ScriptExecuterStub(new ScriptStub(new ScriptStepStub(), new ScriptStepStub(), new ScriptStepStub(false), new ScriptStepStub(false)));

            scriptExecuter.Execute();

            Assert.IsTrue(scriptExecuter.OnExecutionStartedCalled);
            Assert.IsTrue(scriptExecuter.OnExecutionEndedCalled);
            Assert.AreNotEqual(scriptExecuter.Script.Steps.Count(), scriptExecuter.ExecuteStepCallCount);
            Assert.AreEqual(3, scriptExecuter.ExecuteStepCallCount);
            Assert.IsTrue(scriptExecuter.ExecutionEndedWithError);
            StringAssert.Contains(ScriptExecuterStub.FailingStepErrorMessage, scriptExecuter.ExecutionDetails);
        }
        #endregion

        #region AdditionalParameters
        [Test]
        public void GetGeneralAdditionalParameterValues_ShouldReturnParameterForRelease()
        {
            var additionalParameterValues = scriptExecuter.GetAdditionalParameterValues();

            CollectionAssert.AreEquivalent(new string[] { Parameter.ReservedNames.ReleaseName }, additionalParameterValues.Select(x => x.ParameterName));
            CollectionAssert.AreEquivalent(new string[] { ScriptStub.ReleaseStub.Name }, additionalParameterValues.Select(x => x.StringValue));
        }
        #endregion
    }
}
